#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# ABX Comparator
# Copyright (c) 2006 Артём Попов <artfwo@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

import random
import getopt
import sys, os.path

import pygst
pygst.require("0.10")
import gst

import pygtk
pygtk.require("2.0")
import gtk
import gtk.glade
import gobject

import abxer

GLADE_PATH = "abx-comparator.ui"

def factorial(n):
    if n == 0: return 1
    else: return n * factorial(n - 1)

def p_value(n, k):
    p = 0
    for i in range(n - k + 1):
        p += (factorial(n) / (factorial(i) * factorial(n - i))) * \
            (0.5 ** i) * (0.5 **(n - i))
    return p

(ABX_X_IS_NONE,
ABX_X_IS_A,
ABX_X_IS_B) = range(3)

class AbxComparator:
    def __init__(self, a_location=None, b_location=None):
        xml = gtk.glade.XML(GLADE_PATH)
        xml.signal_autoconnect(self)

        self.__a_stream = None
        self.__b_stream = None
        self.__current_stream = None

        self.__x = ABX_X_IS_NONE
        self.__trials = 0
        self.__score = 0

        self.__player = abxer.Looper()
        self.__player.connect("stopped", self.__on_player_stopped)
        self.__player.connect("position-updated", self.__on_player_pos_updated)
        self.__player.connect("error", self.__on_player_error)

        self.__a_chooser = xml.get_widget("a_chooser")
        self.__b_chooser = xml.get_widget("b_chooser")
        self.__a_chooser.connect("selection-changed",
            self._on_a_chooser_selection_changed)
        self.__b_chooser.connect("selection-changed",
            self._on_b_chooser_selection_changed)

        self.__sample_selector = xml.get_widget("sample_selector")
        self.__repeat_button = xml.get_widget("repeat_button")

        self.__a_button = xml.get_widget("a_button")
        self.__b_button = xml.get_widget("b_button")
        self.__x_button = xml.get_widget("x_button")

        self.__stop_button = xml.get_widget("stop_button")
        self.__isa_button = xml.get_widget("isa_button")
        self.__isb_button = xml.get_widget("isb_button")

        self.__results_view = xml.get_widget("results_view")

        self.__results_store = self.__create_results_store()
        self.__results_view.set_model(self.__results_store)

        column = gtk.TreeViewColumn('Result Name',
            gtk.CellRendererText(), text=0)
        self.__results_view.append_column(column)
        column = gtk.TreeViewColumn('Result Value',
            gtk.CellRendererText(), text=1)
        self.__results_view.append_column(column)

        self.__show_results_button = xml.get_widget("show_results_button")
        self.__clear_results_button = xml.get_widget("clear_results_button")

        self.__window = xml.get_widget("abx_audio_window")
        self.__window.show_all()
        self.__update_ui()

        if a_location:
            self.__load_a(a_location)
        if b_location:
            self.__load_b(b_location)

    def _on_main_window_delete_event(self, *args):
        self.__quit()

    def _on_a_chooser_selection_changed(self, button):
        uri = button.get_uri()
        if(uri == None) or \
            (self.__a_stream != None and uri in self.__a_stream):
            return

        self.__load_a(uri)

    def _on_b_chooser_selection_changed(self, button):
        uri = button.get_uri()
        if(uri is None) or \
            (self.__b_stream is not None and uri in self.__b_stream):
            return

        self.__load_b(uri)

    def _on_repeat_button_toggled(self, *args):
        self.__player.props.loop = self.__repeat_button.get_active()

    def _on_a_button_toggled(self, *args):
        if not self.__a_button.get_active():
            # "not active" could be when switching from another button
            #if self.__current_stream == self.__a_stream:
            #       self.__player.stop()
            return

        if self.__player.is_playing():
            self.__player.stop(silent=True)
            self.__b_button.set_active(False)
            self.__x_button.set_active(False)

        self.__play(self.__a_stream)
        self.__update_ui()

    def _on_b_button_toggled(self, *args):
        if not self.__b_button.get_active():
            # "not active" could be when switching from another button
            #if self.__current_stream == self.__b_stream:
            #       self.__player.stop()
            return

        if self.__player.is_playing():
            self.__player.stop(silent=True)
            self.__a_button.set_active(False)
            self.__x_button.set_active(False)

        self.__play(self.__b_stream)
        self.__update_ui()

    def _on_x_button_toggled(self, *args):
        if not self.__x_button.get_active():
            # "not active" could be when switching from another button
            return

        if self.__player.is_playing():
            self.__player.stop(silent=True)
            self.__a_button.set_active(False)
            self.__b_button.set_active(False)

        if self.__x == ABX_X_IS_NONE:
            if random.randint(0, 1):
                self.__x = ABX_X_IS_A
            else: self.__x = ABX_X_IS_B

        if self.__x == ABX_X_IS_A:
            self.__play(self.__a_stream)
        else: self.__play(self.__b_stream)

        self.__update_ui()

    def _on_stop_button_clicked(self, *args):
        self.__player.stop()

    def _on_isa_button_clicked(self, *args):
        if self.__player.is_playing():
            self.__player.stop()

        self.__trials += 1
        if self.__x == ABX_X_IS_A:
            self.__score += 1

        self.__x = ABX_X_IS_NONE
        self.__update_ui()

    def _on_isb_button_clicked(self, *args):
        if self.__player.is_playing():
            self.__player.stop()

        self.__trials += 1
        if self.__x == ABX_X_IS_B:
            self.__score += 1

        self.__x = ABX_X_IS_NONE
        self.__update_ui()

    def _on_show_results_button_toggled(self, *args):
        self.__update_ui()

    def _on_clear_results_button_clicked(self, *args):
        self.__trials = 0
        self.__score = 0
        self.__update_ui()

    def _on_quit_button_clicked(self, *args):
        self.__quit()

    def __on_player_stopped(self, player):
        self.__sample_selector.set_sensitive(True)
        self.__update_ui()

        self.__sample_selector.playback_position = 0.0
        self.__sample_selector.queue_draw()

        self.__current_stream = None

    def __on_player_pos_updated(self, player, position):
        location, duration = self.__current_stream
        pos_value = float(position) / duration

        self.__sample_selector.playback_position = pos_value
        self.__sample_selector.queue_draw()

    def __on_player_error(self, value):
        error, debug = value
        self.__error_message(error, debug)

    def __error_message(self, message, secondary):
        dialog = gtk.MessageDialog(self.__window,
            gtk.DIALOG_MODAL,
            gtk.MESSAGE_ERROR,
            gtk.BUTTONS_OK,
            message)
        dialog.format_secondary_text(secondary)
        dialog.run()
        dialog.destroy()

    def __load_a(self, location):
        if self.__current_stream == self.__a_stream:
            self.__player.stop()
        try:
            metadata = abxer.Metadata(location)
            duration = metadata[gst.TAG_DURATION]
            # FIXME: this may cause chooser to call me again
            self.__a_chooser.select_uri(location)
            self.__a_stream = (location, duration)
            # FIXME: not "Exception, value" but the new Python style
        except Exception, value:
            # message, secondary = value
            print type(value)
	    print str(value)
            self.__a_chooser.unselect_uri(location)
            self.__a_stream = None
        self.__update_ui()

    def __load_b(self, location):
        if self.__current_stream == self.__b_stream:
            self.__player.stop()
        try:
            metadata = abxer.Metadata(location)
            duration = metadata[gst.TAG_DURATION]
            # FIXME: this may cause chooser to call me again
            self.__b_chooser.select_uri(location)
            self.__b_stream = (location, duration)
            # FIXME: not "Exception, value" but the new Python style
        except Exception as value:
            # message = value, secondary = value
            print type(value)
	    print str(value)
            self.__b_chooser.unselect_uri(location)
            self.__b_stream = None
        self.__update_ui()

    def __create_results_store(self):
        store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)

        iter = store.append()
        store.set(iter, 0, "Trials")
        iter = store.append()
        store.set(iter, 0, "Score")
        iter = store.append()
        store.set(iter, 0, "P-value")

        return store

    def __play(self, stream):
        # FIXME: track this down and delete if stopped
        self.__current_stream = stream
        location, duration = self.__current_stream
        self.__player.load(location)

        v1 = self.__sample_selector.start_value * duration
        v2 = self.__sample_selector.stop_value * duration
        self.__player.set_segment(long(v1), long(v2))

        self.__player.play()
        self.__sample_selector.set_sensitive(False)

    def __update_ui(self):
        if self.__a_stream:
            self.__a_button.set_sensitive(True)
        else: self.__a_button.set_sensitive(False)

        if self.__b_stream:
            self.__b_button.set_sensitive(True)
        else: self.__b_button.set_sensitive(False)

        if self.__a_stream and self.__b_stream:
            self.__x_button.set_sensitive(True)
        else: self.__x_button.set_sensitive(False)

        self.__stop_button.set_sensitive(self.__player.is_playing())

        if self.__x == ABX_X_IS_NONE:
            self.__isa_button.set_sensitive(False)
            self.__isb_button.set_sensitive(False)
        else:
            self.__isa_button.set_sensitive(True)
            self.__isb_button.set_sensitive(True)

        if self.__trials > 0:
            self.__clear_results_button.set_sensitive(True)
        else: self.__clear_results_button.set_sensitive(False)

        if not self.__player.is_playing():
            self.__a_button.set_active(False)
            self.__b_button.set_active(False)
            self.__x_button.set_active(False)

        # results
        
        if self.__show_results_button.get_active():
            score_value = str(self.__score)
            p = "%.2f%%" % round(p_value(self.__trials, self.__score) * 100, 2)
        else:
            score_value, p = "hidden", "******"

        iter = self.__results_store.get_iter_first()
        self.__results_store.set(iter, 1, self.__trials)

        iter = self.__results_store.iter_next(iter)
        self.__results_store.set(iter, 1, score_value)

        iter = self.__results_store.iter_next(iter)
        self.__results_store.set(iter, 1, p)

    def __quit(self):
        if self.__player.is_playing():
            self.__player.stop()
        gtk.main_quit()

def glade_custom_handler(xml, *args):
    return abxer.SampleSelector() # we only have 1 custom widget

if __name__ == "__main__":
    gtk.glade.set_custom_handler(glade_custom_handler)
    #gtk.window_set_default_icon_name("abx-comparator")
    gtk.window_set_default_icon_from_file("abx-comparator.svg")

    if 2 < len(sys.argv) <= 3:
        a = sys.argv[1]
        if not gst.uri_is_valid(a):
            a = "file://" + os.path.abspath(a)
    else:
        a = None

    if len(sys.argv) == 3:
        b = sys.argv[2]
        if not gst.uri_is_valid(b):
            b = "file://" + os.path.abspath(b)
    else:
        b = None

    app = AbxComparator(a, b)
    gtk.main()
